`include "defines.svh"

`define CSR_MSTATUS             csr_array[`CSR_ADDR_MSTATUS]
`define CSR_MIE                 csr_array[`CSR_ADDR_MIE]
`define CSR_MTVEC               csr_array[`CSR_ADDR_MTVEC]
`define CSR_MEPC                csr_array[`CSR_ADDR_MEPC]
`define CSR_MCAUSE              csr_array[`CSR_ADDR_MCAUSE]
`define CSR_MIP                 csr_array[`CSR_ADDR_MIP]

`define CSR_MIP_MSIP            `CSR_MIP[3]
`define CSR_MIP_MTIP            `CSR_MIP[7]
`define CSR_MIP_MEIP            `CSR_MIP[11]

`define CSR_MIE_MSIE            `CSR_MIE[3]
`define CSR_MIE_MTIE            `CSR_MIE[7]
`define CSR_MIE_MEIE            `CSR_MIE[11]

`define CSR_MSTATUS_MIE         `CSR_MSTATUS[3]
`define CSR_MSTATUS_MPIE        `CSR_MSTATUS[7]
`define CSR_MSTATUS_MPP         `CSR_MSTATUS[12:11]


module CSR (
    input   logic                       clock,
    input   logic                       reset,

    input   logic [11:0]                csr_raddr,
    output  logic [63:0]                csr_rdata,

    // StageWB -> CSR
    input   logic [`WB2CSR_BUS_WD-1:0]  wb2csr_bus,

    // CSR -> All Stages
    output  logic                       csr_taken,
    // CSR -> StageIF
    output  logic [63:0]                csr_target,

    // CLINT -> CSR @ Interrupt
    input   logic                       clint_msi,
    input   logic                       clint_mti,

    // CSR -> StageID @ Interrupt
    output  logic [1:0]                 csr_intr
);

    logic [63:0]    csr_array [4095:0];

    // Initialize CSR
    import "DPI-C" function void dpic_set_csr_ptr(input logic [63:0] a []);
    initial dpic_set_csr_ptr(csr_array);

    // Read CSR
    assign csr_rdata = csr_array[csr_raddr];

/* ---------------------------- StageWB -> CSR ---------------------------- */
    logic           csr_we;
    logic [11:0]    csr_waddr;
    logic [63:0]    csr_wdata;
    logic           mret_taken;
    logic           trap_taken;
    logic [63:0]    trap_cause;
    logic [63:0]    trap_pc;
    logic [31:0]    trap_inst;

    assign { 
        csr_we, 
        csr_waddr, 
        csr_wdata, 
        mret_taken, 
        trap_taken, 
        trap_cause, 
        trap_pc, 
        trap_inst 
    } = wb2csr_bus;
/* ---------------------------- StageWB -> CSR ---------------------------- */


/* ------------------------------- MSTATUS ------------------------------- */
    always_ff @ (posedge clock) begin
        if (reset) begin
            `CSR_MSTATUS    <= 64'h000001800;
        end
        else if (trap_taken) begin
            // $display("TRAP: PC=%h, mstatus.MPIE=%b, mstatus.MIE=%b, csr_taken=%b, csr_target=%h", trap_pc, `CSR_MSTATUS_MPIE, `CSR_MSTATUS_MIE, csr_taken, csr_target);
            `CSR_MSTATUS_MPIE   <= `CSR_MSTATUS_MIE;
            `CSR_MSTATUS_MIE    <= 1'b0;
            `CSR_MSTATUS_MPP    <= 2'b11;
        end
        else if (mret_taken) begin
            // $display("MRET: PC=%h, mstatus.MPIE=%b, mstatus.MIE=%b, csr_taken=%b, csr_target=%h", `CSR_MEPC, `CSR_MSTATUS_MPIE, `CSR_MSTATUS_MIE, csr_taken, csr_target);
            `CSR_MSTATUS_MIE    <= `CSR_MSTATUS_MPIE;
            `CSR_MSTATUS_MPIE   <= 1'b1; /// ???
        end
        else if (csr_we & csr_waddr==`CSR_ADDR_MSTATUS) begin
            `CSR_MSTATUS    <= csr_wdata;
        end
    end
/* ------------------------------- MSTATUS ------------------------------- */


/* ------------------------------- MIE ------------------------------- */
    always_ff @ (posedge clock) begin
        if (reset) begin
            `CSR_MIE    <= 64'h0;
        end
        else if (csr_we & csr_waddr==`CSR_ADDR_MIE) begin
            `CSR_MIE    <= csr_wdata;
        end
    end
/* ------------------------------- MIE ------------------------------- */


/* ------------------------------- MTVEC ------------------------------- */
    always_ff @ (posedge clock) begin
        if (reset) begin
            `CSR_MTVEC  <= 64'h0;
        end
        else if (csr_we & csr_waddr==`CSR_ADDR_MTVEC) begin
            `CSR_MTVEC  <= csr_wdata;
        end
    end
/* ------------------------------- MTVEC ------------------------------- */


/* ------------------------------- MEPC ------------------------------- */
    always_ff @ (posedge clock) begin
        if (reset) begin
            `CSR_MEPC   <= 64'h0;
        end
        else if (trap_taken) begin
            `CSR_MEPC   <= trap_pc;
        end
        else if (csr_we & csr_waddr==`CSR_ADDR_MEPC) begin
            `CSR_MEPC   <= csr_wdata;
        end
    end
/* ------------------------------- MEPC ------------------------------- */


/* ------------------------------- MCAUSE ------------------------------- */
    always_ff @ (posedge clock) begin
        if (reset) begin
            `CSR_MCAUSE <= 64'h0;
        end
        else if (trap_taken) begin
            `CSR_MCAUSE <= trap_cause;
        end
        else if (csr_we & csr_waddr==`CSR_ADDR_MCAUSE) begin
            `CSR_MCAUSE <= csr_wdata;
        end
    end
/* ------------------------------- MCAUSE ------------------------------- */


/* -------------------------------- MIP -------------------------------- */
    always_ff @ (posedge clock) begin
        if (reset) begin
            `CSR_MIP        <= 64'h0;
        end
        else begin
            `CSR_MIP_MSIP   <= clint_msi;
            `CSR_MIP_MTIP   <= clint_mti;
            `CSR_MIP_MEIP   <= 1'b0;
        end
    end
/* -------------------------------- MIP -------------------------------- */


/* ---------------------- CSR -> StageID @ Interrupt ---------------------- */
    assign csr_intr[0]  = `CSR_MIE_MSIE & `CSR_MIP_MSIP & `CSR_MSTATUS_MIE;
    assign csr_intr[1]  = `CSR_MIE_MTIE & `CSR_MIP_MTIP & `CSR_MSTATUS_MIE;
/* ---------------------- CSR -> StageID @ Interrupt ---------------------- */


/* ------------------------------ CSR -> All Stages ------------------------------ */
    assign csr_taken    = mret_taken | trap_taken;
/* ------------------------------ CSR -> All Stages ------------------------------ */

/* -------------------------------- CSR -> StageIF -------------------------------- */
    assign csr_target   = mret_taken ? `CSR_MEPC : `CSR_MTVEC ;
/* -------------------------------- CSR -> StageIF -------------------------------- */

    import "DPI-C" function void dpic_take_trap(input longint cause, input longint pc);

    always_ff @ (posedge clock) begin
        if (!reset & trap_taken) begin
            if (trap_cause != 11) $display("Exception: pc=%H, inst=%H, cause=%H", trap_pc, trap_inst, trap_cause);
            dpic_take_trap(trap_cause, trap_pc);
        end
    end

endmodule
